{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CNN on MNIST digits classification\n",
    "\n",
    "This example is the same as the MLP for MNIST classification. The difference is we are going to use `Conv2D` layers instead of `Dense` layers.\n",
    "\n",
    "The model that will be costructed below is made of:\n",
    "\n",
    "- First 2 layers - `Conv2D-ReLU-MaxPool`\n",
    "- 3rd layer - `Conv2D-ReLU`\n",
    "- 4th layer - `Dense(10)`\n",
    "- Output Activation - `softmax`\n",
    "- Optimizer - `SGD`\n",
    "\n",
    "Let us first load the packages and perform the initial pre-processing such as loading the dataset, performing normalization and conversion of labels to one-hot.\n",
    "\n",
    "Recall that in our `3-Dense` MLP example, we achieved ~95.3% accuracy at 269k parameters. Here, we can achieve ~98.5% using 105k parameters. CNN is more parameter efficient."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import absolute_import\n",
    "from __future__ import division\n",
    "from __future__ import print_function\n",
    "\n",
    "import numpy as np\n",
    "from tensorflow.keras.models import Sequential\n",
    "from tensorflow.keras.layers import Activation, Dense, Dropout\n",
    "from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten\n",
    "from tensorflow.keras.utils import to_categorical, plot_model\n",
    "from tensorflow.keras.datasets import mnist\n",
    "\n",
    "# load mnist dataset\n",
    "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
    "\n",
    "# compute the number of labels\n",
    "num_labels = len(np.unique(y_train))\n",
    "\n",
    "# convert to one-hot vector\n",
    "y_train = to_categorical(y_train)\n",
    "y_test = to_categorical(y_test)\n",
    "\n",
    "# input image dimensions\n",
    "image_size = x_train.shape[1]\n",
    "# resize and normalize\n",
    "x_train = np.reshape(x_train,[-1, image_size, image_size, 1])\n",
    "x_test = np.reshape(x_test,[-1, image_size, image_size, 1])\n",
    "x_train = x_train.astype('float32') / 255\n",
    "x_test = x_test.astype('float32') / 255"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Hyper-parameters\n",
    "\n",
    "This hyper-parameters are similar to our MLP example. The differences are `kernel_size = 3` which is a typical kernel size in most CNNs and `filters = 64`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# network parameters\n",
    "# image is processed as is (square grayscale)\n",
    "input_shape = (image_size, image_size, 1)\n",
    "batch_size = 128\n",
    "kernel_size = 3\n",
    "filters = 64"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sequential Model Building\n",
    "\n",
    "The model is similar to our previous example in MLP. The difference is we use `Conv2D` instead of `Dense`. Note that due to mismatch in dimensions, the output of the last `Conv2D` is flattened via `Flatten()` layer to suit the input vector dimensions of the `Dense`. Note that though we use `Activation(softmax)` as the last layer, this can also be integrated within the `Dense` layer in the parameter `activation='softmax'`. Both are the same."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential_4\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "conv2d_12 (Conv2D)           (None, 28, 28, 64)        640       \n",
      "_________________________________________________________________\n",
      "max_pooling2d_8 (MaxPooling2 (None, 14, 14, 64)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_13 (Conv2D)           (None, 14, 14, 64)        36928     \n",
      "_________________________________________________________________\n",
      "max_pooling2d_9 (MaxPooling2 (None, 7, 7, 64)          0         \n",
      "_________________________________________________________________\n",
      "conv2d_14 (Conv2D)           (None, 7, 7, 64)          36928     \n",
      "_________________________________________________________________\n",
      "flatten_4 (Flatten)          (None, 3136)              0         \n",
      "_________________________________________________________________\n",
      "dense_4 (Dense)              (None, 10)                31370     \n",
      "_________________________________________________________________\n",
      "activation_4 (Activation)    (None, 10)                0         \n",
      "=================================================================\n",
      "Total params: 105,866\n",
      "Trainable params: 105,866\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "# model is a stack of CNN-ReLU-MaxPooling\n",
    "model = Sequential()\n",
    "model.add(Conv2D(filters=filters,\n",
    "                 kernel_size=kernel_size,\n",
    "                 activation='relu',\n",
    "                 padding='same',\n",
    "                 input_shape=input_shape))\n",
    "model.add(MaxPooling2D())\n",
    "model.add(Conv2D(filters=filters,\n",
    "                 kernel_size=kernel_size,\n",
    "                 padding='same',\n",
    "                 activation='relu'))\n",
    "model.add(MaxPooling2D())\n",
    "model.add(Conv2D(filters=filters,\n",
    "                 kernel_size=kernel_size,\n",
    "                 padding='same',\n",
    "                 activation='relu'))\n",
    "model.add(Flatten())\n",
    "# dropout added as regularizer\n",
    "# model.add(Dropout(dropout))\n",
    "# output layer is 10-dim one-hot vector\n",
    "model.add(Dense(num_labels))\n",
    "model.add(Activation('softmax'))\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Training and Evaluation\n",
    "\n",
    "After building the model, it is time to train and evaluate. This part is similar to MLP training and evaluation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/20\n",
      "469/469 [==============================] - 44s 93ms/step - loss: 1.2029 - accuracy: 0.6349\n",
      "Epoch 2/20\n",
      "469/469 [==============================] - 44s 94ms/step - loss: 0.3460 - accuracy: 0.8959\n",
      "Epoch 3/20\n",
      "469/469 [==============================] - 44s 94ms/step - loss: 0.2564 - accuracy: 0.9221\n",
      "Epoch 4/20\n",
      "469/469 [==============================] - 44s 93ms/step - loss: 0.2008 - accuracy: 0.9401\n",
      "Epoch 5/20\n",
      "469/469 [==============================] - 44s 95ms/step - loss: 0.1630 - accuracy: 0.9521\n",
      "Epoch 6/20\n",
      "469/469 [==============================] - 45s 95ms/step - loss: 0.1369 - accuracy: 0.9588\n",
      "Epoch 7/20\n",
      "469/469 [==============================] - 44s 95ms/step - loss: 0.1176 - accuracy: 0.9652\n",
      "Epoch 8/20\n",
      "469/469 [==============================] - 44s 94ms/step - loss: 0.1046 - accuracy: 0.9686\n",
      "Epoch 9/20\n",
      "469/469 [==============================] - 45s 96ms/step - loss: 0.0940 - accuracy: 0.9717\n",
      "Epoch 10/20\n",
      "469/469 [==============================] - 45s 95ms/step - loss: 0.0862 - accuracy: 0.9741\n",
      "Epoch 11/20\n",
      "469/469 [==============================] - 45s 95ms/step - loss: 0.0798 - accuracy: 0.9757\n",
      "Epoch 12/20\n",
      "469/469 [==============================] - 44s 95ms/step - loss: 0.0748 - accuracy: 0.9776\n",
      "Epoch 13/20\n",
      "469/469 [==============================] - 44s 95ms/step - loss: 0.0700 - accuracy: 0.9793\n",
      "Epoch 14/20\n",
      "469/469 [==============================] - 45s 95ms/step - loss: 0.0664 - accuracy: 0.9801\n",
      "Epoch 15/20\n",
      "469/469 [==============================] - 44s 94ms/step - loss: 0.0636 - accuracy: 0.9808\n",
      "Epoch 16/20\n",
      "469/469 [==============================] - 45s 96ms/step - loss: 0.0604 - accuracy: 0.9815\n",
      "Epoch 17/20\n",
      "469/469 [==============================] - 45s 96ms/step - loss: 0.0577 - accuracy: 0.9822\n",
      "Epoch 18/20\n",
      "469/469 [==============================] - 46s 99ms/step - loss: 0.0560 - accuracy: 0.9825\n",
      "Epoch 19/20\n",
      "469/469 [==============================] - 46s 98ms/step - loss: 0.0533 - accuracy: 0.9841\n",
      "Epoch 20/20\n",
      "469/469 [==============================] - 51s 108ms/step - loss: 0.0516 - accuracy: 0.9843\n",
      "79/79 [==============================] - 2s 23ms/step - loss: 0.0455 - accuracy: 0.9848\n",
      "\n",
      "Test accuracy: 98.5%\n"
     ]
    }
   ],
   "source": [
    "#plot_model(model, to_file='cnn-mnist.png', show_shapes=True)\n",
    "\n",
    "# loss function for one-hot vector\n",
    "# use of adam optimizer\n",
    "# accuracy is good metric for classification tasks\n",
    "model.compile(loss='categorical_crossentropy',\n",
    "              optimizer='sgd',\n",
    "              metrics=['accuracy'])\n",
    "# train the network\n",
    "model.fit(x_train, y_train, epochs=20, batch_size=batch_size)\n",
    "\n",
    "loss, acc = model.evaluate(x_test, y_test, batch_size=batch_size)\n",
    "print(\"\\nTest accuracy: %.1f%%\" % (100.0 * acc))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
